﻿// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone

using BlazorAdmin.Caching;
using BlazorAdmin.Extensions;
using BlazorAdmin.Models;
using BlazorAdmin.Security.Cryptography;
using BlazorAdmin.Interface;
using BootstrapBlazor.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BlazorAdmin.Services.DbService;

namespace BlazorAdmin.Services
{
    internal class UserService : BaMgmtDbService, IUser
    {
        private const string UserServiceGetAppIdByUserNameCacheKey = $"{nameof(UserService)}-GetAppIdByUserName";
        private const string UserServiceGetUserByUserNameCacheKey = $"{nameof(UserService)}-GetUserByUserName";
        private const string UserServiceGetAppsByUserNameCacheKey = $"{nameof(UserService)}-GetAppsByUserName";
        private const string UserServiceGetRolesByUserNameCacheKey = $"{nameof(UserService)}-GetRolesByUserName";
        private const string UserServiceGetUsersByGroupIdCacheKey = $"{nameof(UserService)}-GetUsersByGroupId";
        private const string UserServiceGetUsersByRoleIDCacheKey = $"{nameof(UserService)}-GetUsersByRoleID";
        public UserService(IPetaPoco db) : base(db) { }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public List<User> GetAll() => Fetch<User>();

        /// <summary>
        /// 用户认证
        /// </summary>
        /// <param name="userName">登录名</param>
        /// <param name="password">登录密码</param>
        /// <returns></returns>
        public User Authenticate(string userName, string password)
        {
            User user = GetUserByUserName(userName);
            if (null == user)
                return default;
            string confirmPwd = LgbCryptography.ComputeHash(password, user.PassSalt);
            return confirmPwd == user.Password ? user : default;
        }

        /// <summary>
        /// 用户认证
        /// </summary>
        /// <param name="user">用户信息实体</param>
        /// <param name="password">登录密码</param>
        /// <returns></returns>
        public bool Authenticate(User user, string password)
        {
            if (null == user)
                return default;
            string confirmPwd = LgbCryptography.ComputeHash(password, user.PassSalt);
            return confirmPwd == user.Password;
        }

        public User GetUserByUserName(string userName) => CacheManager.GetOrAdd($"{UserServiceGetUserByUserNameCacheKey}-{userName}",
            entry => string.IsNullOrEmpty(userName) ? null : FirstOrDefault<User>("WHERE UserName = @0", userName));


        public List<string> GetApps(string userName) => CacheManager.GetOrAdd($"{UserServiceGetAppsByUserNameCacheKey}-{userName}",
            entry => Fetch<string>($@"SELECT d.Code FROM Dicts d INNER JOIN RoleApp ra ON d.Code = ra.AppID INNER JOIN (
	                                SELECT r.ID FROM Roles r INNER JOIN UserRole ur ON r.ID = ur.RoleID 
	                                INNER JOIN Users u ON ur.UserID = u.ID WHERE u.UserName = @0 
	                                UNION 
	                                SELECT r.ID FROM Roles r INNER JOIN RoleGroup rg ON r.ID = rg.RoleID 
	                                INNER JOIN SysGroups g ON rg.GroupID = g.ID INNER JOIN UserGroup ug ON ug.GroupID = g.ID 
	                                INNER JOIN Users u ON ug.UserID = u.ID WHERE u.UserName = @0) r ON ra.RoleID = r.ID 
	                                UNION 
	                                SELECT Code FROM Dicts WHERE Category = @1 AND EXISTS(
	                                SELECT r.ID FROM Roles r INNER JOIN UserRole ur ON r.ID = ur.RoleID 
	                                INNER JOIN Users u ON ur.UserID = u.ID WHERE u.UserName = @0 AND r.RoleName = @2 
	                                UNION SELECT r.ID 
	                                FROM Roles r INNER JOIN RoleGroup rg ON r.ID = rg.RoleID 
	                                INNER JOIN SysGroups g ON rg.GroupID = g.ID INNER JOIN UserGroup ug ON ug.GroupID = g.ID 
	                                INNER JOIN Users u ON ug.UserID = u.ID WHERE u.UserName = @0 AND r.RoleName = @2)", userName, "应用程序", "Administrators"));

        /// <summary>
        /// 通过用户名获得指定的前台 AppId
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public string GetAppIdByUserName(string userName)
        {
            return CacheManager.GetOrAdd($"{UserServiceGetAppIdByUserNameCacheKey}-{userName}",
                entry => FirstOrDefault<User>("Where UserName = @0", userName)?.App);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public List<string> GetRoles(string userName) => CacheManager.GetOrAdd($"{UserServiceGetRolesByUserNameCacheKey}-{userName}",
            entry => Fetch<string>(@"SELECT r.RoleName FROM Roles r INNER JOIN UserRole ur ON r.ID=ur.RoleID 
                                     INNER JOIN Users u ON ur.UserID = u.ID AND u.UserName = @0 
                                     UNION 
                                     SELECT r.RoleName FROM Roles r INNER JOIN RoleGroup rg ON r.ID = rg.RoleID 
                                     INNER JOIN SysGroups g ON rg.GroupID = g.ID INNER JOIN UserGroup ug ON ug.GroupID = g.ID 
                                     INNER JOIN Users u ON ug.UserID = u.ID AND u.UserName = @0", userName));


        /// <summary>
        /// 
        /// </summary>
        /// <param name="groupId"></param>
        public List<int> GetUsersByGroupId(int groupId) => CacheManager.GetOrAdd($"{UserServiceGetUsersByGroupIdCacheKey}-{groupId}",
            entry => Fetch<int>("SELECT UserID FROM UserGroup WHERE GroupID = @0", groupId));

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="userIds"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public bool SaveUsersByGroupId(int id, IEnumerable<int> userIds)
        {
            var ret = false;
            using var db = Create();
            try
            {
                db.BeginTransaction();
                db.Execute("DELETE FROM UserGroup WHERE GroupID = @0", id);
                db.InsertBatch("UserGroup", userIds.Select(g => new { UserID = g, GroupID = id }));
                db.CompleteTransaction();
                ret = true;
            }
            catch (Exception)
            {
                db.AbortTransaction();
                throw;
            }
            if (ret)
            {
                CacheManager.Clear();
            }
            return ret;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="roleId"></param>
        /// <returns></returns>
        public List<int> GetUsersByRoleId(int roleId) => CacheManager.GetOrAdd($"{UserServiceGetUsersByRoleIDCacheKey}-{roleId}",
            entry => Fetch<int>("SELECT UserID FROM UserRole WHERE RoleID = @0", roleId));

        /// <summary>
        /// 
        /// </summary>
        /// <param name="roleId"></param>
        /// <param name="userIds"></param>
        /// <returns></returns>
        public bool SaveUsersByRoleId(int roleId, IEnumerable<int> userIds)
        {
            var ret = false;
            using var db = Create();
            try
            {
                db.BeginTransaction();
                db.Execute("DELETE FROM UserRole WHERE RoleID = @0", roleId);
                db.InsertBatch("UserRole", userIds.Select(g => new { UserID = g, RoleID = roleId }));
                db.CompleteTransaction();
                ret = true;
            }
            catch (Exception)
            {
                db.AbortTransaction();
                throw;
            }
            if (ret)
            {
                CacheManager.Clear();
            }
            return ret;
        }

        /// <summary>
        /// 更新密码方法
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="newPassword"></param>
        public bool ChangePassword(string userName, string password, string newPassword)
        {
            var ret = false;
            if (null != Authenticate(userName, password))
            {
                var passSalt = LgbCryptography.GenerateSalt();
                string sql = "SET Password = @0, PassSalt = @1 WHERE UserName = @2";
                ret = Update<User>(sql, LgbCryptography.ComputeHash(newPassword, passSalt), passSalt, userName) == 1;
                if (ret)
                    CacheManager.Clear($"{UserServiceGetUserByUserNameCacheKey}-{userName}");
            }
            return ret;
        }

        /// <summary>
        /// 
        /// </summary>
        public bool SaveDisplayName(string userName, string displayName)
        {
            using var db = Create();
            var ret = db.Update<User>("SET DisplayName = @1 WHERE UserName = @0", userName, displayName) == 1;
            if (ret)
            {
                CacheManager.Clear($"{UserServiceGetUserByUserNameCacheKey}-{userName}");
            }
            return ret;
        }

        /// <summary>
        /// 
        /// </summary>
        public bool SaveLogo(string userName, string logo)
        {
            using var db = Create();
            var ret = db.Update<User>("SET Icon = @1 WHERE UserName = @0", userName, logo) == 1;
            if (ret)
            {
                CacheManager.Clear($"{UserServiceGetUserByUserNameCacheKey}-{userName}");
            }
            return ret;
        }

        /// <summary>
        /// 保存用户
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="password">用户密码</param>
        /// <param name="displayName">显示名称</param>
        /// <returns></returns>
        public bool SaveUser(string userName, string password, string displayName)
        {
            var salt = LgbCryptography.GenerateSalt();
            var pwd = LgbCryptography.ComputeHash(password, salt);
            using var db = Create();
            var user = db.FirstOrDefault<User>("WHERE UserName = @0", userName);
            bool ret;
            if (user == null)
            {
                try
                {
                    // 开始事务
                    db.BeginTransaction();
                    user = new User()
                    {
                        DisplayName = displayName,
                        UserName = userName,
                        PassSalt = salt,
                        Password = pwd
                    };
                    db.Save(user);
                    // 授权 Default 角色
                    db.Execute("INSERT INTO UserRole(UserID,RoleID) SELECT ID, (SELECT ID FROM Roles WHERE RoleName = 'Default') RoleID FROM Users WHERE UserName = @0", userName);
                    // 结束事务
                    db.CompleteTransaction();
                    ret = true;
                }
                catch (Exception)
                {
                    db.AbortTransaction();
                    throw;
                }
            }
            else
            {
                user.DisplayName = displayName;
                user.PassSalt = salt;
                user.Password = pwd;
                db.Update(user);
                ret = true;
            }
            if (ret)
            {
                CacheManager.Clear($"{UserServiceGetUserByUserNameCacheKey}-{userName}");
            }
            return ret;
        }

        public bool SaveApp(string userName, string app)
        {
            using var db = Create();
            var ret = db.Update<User>("SET App = @1 WHERE UserName = @0", userName, app) == 1;
            if (ret)
            {
                CacheManager.Clear($"{UserServiceGetUserByUserNameCacheKey}-{userName}");
            }
            return ret;
        }

        public Task<QueryData<User>> Query(AppQueryOption<string> query)
        {
            var strSql = @"select usr.*, org.OrgName from Users usr 
                           left join UserOrganize usr_org on usr.UserName = usr_org.UserName
                           left join Organizes org on usr_org.OrgCode = org.OrgCode";

            if (!string.IsNullOrWhiteSpace(query.Option.SearchText)) // 快速搜索
            {
                var searchTxt = query.Option.SearchText.Trim();
                if (Utils.IsChinese(searchTxt)) // 显示名称
                    strSql += $" and usr.DisplayName like '%{searchTxt}%'";
                else // 用户账户
                    strSql += $" and usr.UserName like '%{searchTxt}%'";
            }
            else if (query.Option.SearchModel != null) // 高级搜索
            {
                var param = (User)query.Option.SearchModel;
                if (!string.IsNullOrWhiteSpace(param.DisplayName))
                    strSql += $" and usr.DisplayName like '%{param.DisplayName.Trim()}%'";
                if (!string.IsNullOrWhiteSpace(param.UserName))
                    strSql += $" and usr.UserName like '%{param.UserName.Trim()}%'";
                if (!string.IsNullOrWhiteSpace(param.OrgName))
                    strSql += $" and org.OrgName like '%{param.OrgName}%'";
            }

            query.Sql = new(strSql);

            // 注意：大写的表名，必须有双引号
            query.Option.SortList.Add("UserName");

            return PageQueryAsync<User>(query);
        }

        public bool SaveUserOrg(string userName, string orgCode)
        {
            var strSql = @"do $$ begin if exists (select 1 from UserOrganize where UserName = '{0}') then
	                           update UserOrganize set OrgCode = '{1}' where UserName = '{0}';
                           else
	                           insert into UserOrganize (UserName, OrgCode) values ('{0}', '{1}');
                           end if; END $$;";

            Create().Execute(string.Format(strSql, userName, orgCode));

            return true;
        }
    }
}